import { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js";
import { findStripePriceFromPrices } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
import { CusService } from "@/internal/customers/CusService.js";
import { isV4Usage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
import {
  AppEnv,
  AttachBranch,
  AttachPreview,
  FullCusProduct,
  FullCustomer,
  Organization,
  ProductV2,
} from "@autumn/shared";
import { expect } from "chai";
import Stripe from "stripe";
import { expectSubItemsCorrect } from "./expectSubUtils.js";
import { expectProductAttached } from "./expectProductAttached.js";
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";
import { advanceTestClock } from "../stripeUtils.js";
import { hoursToFinalizeInvoice } from "../constants.js";
import { addHours } from "date-fns";

export const expectNextCycleCorrect = async ({
  autumn,
  preview,
  stripeCli,
  customerId,
  testClockId,
  product,
  db,
  org,
  env,
  advanceClock = true,
}: {
  autumn: AutumnInt;
  preview: AttachPreview;
  stripeCli: Stripe;
  customerId: string;
  testClockId: string;
  product: ProductV2;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
  advanceClock?: boolean;
}) => {
  if (advanceClock) {
    await advanceTestClock({
      stripeCli,
      testClockId,
      advanceTo: addHours(
        preview!.due_next_cycle.due_at,
        hoursToFinalizeInvoice,
      ).getTime(),
    });
  }

  const customer = await autumn.customers.get(customerId);

  expectProductAttached({
    customer,
    product,
  });

  await expectSubItemsCorrect({
    stripeCli,
    customerId,
    product,
    db,
    org,
    env,
  });
};

export const expectDowngradeCorrect = async ({
  customerId,
  curProduct,
  newProduct,
  autumn,
  stripeCli,
  db,
  org,
  env,
}: {
  customerId: string;
  curProduct: ProductV2;
  newProduct: ProductV2;
  autumn: AutumnInt;
  stripeCli: Stripe;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
}) => {
  const preview = await autumn.attachPreview({
    customer_id: customerId,
    product_id: newProduct.id,
  });

  await autumn.attach({
    customer_id: customerId,
    product_id: newProduct.id,
  });

  const customer = await autumn.customers.get(customerId);

  const productCount = customer.products.reduce((acc: number, product: any) => {
    if (product.group == curProduct.group) {
      return acc + 1;
    } else return acc;
  }, 0);

  expect(
    productCount,
    "customer should only have 2 products (from this group)",
  ).to.equal(2);

  expectProductAttached({
    customer,
    product: curProduct,
  });

  const { fullCus } = await expectSubItemsCorrect({
    stripeCli,
    customerId,
    product: curProduct,
    db,
    org,
    env,
    isCanceled: true,
  });

  const newProductIsFree = isFreeProductV2({ product: newProduct });

  if (newProductIsFree) {
    let res = await stripeCli.subscriptionSchedules.list({
      customer: fullCus.processor?.id,
    });

    let data = res.data.filter((s) => s.status != "canceled");
    expect(data.length, "should have no sub schedules").to.equal(0);

    await expectSubScheduleCorrect({
      stripeCli,
      customerId,
      productId: newProduct.id,
      db,
      org,
      env,
    });

    expectProductAttached({
      customer,
      product: newProduct,
    });
  }

  expect(preview.branch).to.equal(AttachBranch.Downgrade);

  return {
    preview,
  };
};

export const expectSubScheduleCorrect = async ({
  stripeCli,
  customerId,
  productId,
  db,
  org,
  env,
  fullCus,
}: {
  stripeCli: Stripe;
  customerId: string;
  productId: string;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
  fullCus?: FullCustomer;
}) => {
  // 1. Check schedule
  if (!fullCus) {
    fullCus = await CusService.getFull({
      db,
      idOrInternalId: customerId,
      orgId: org.id,
      env,
    });
  }

  const cusProduct = fullCus.customer_products.find(
    (cp: FullCusProduct) => cp.product.id == productId,
  )!;

  const scheduleSets = await getStripeSchedules({
    stripeCli,
    scheduleIds: cusProduct?.scheduled_ids || [],
  });

  const stripePrices = scheduleSets.flatMap(
    (schedule) => schedule?.prices || [],
  );

  const autumnPrices = cusProductToPrices({ cusProduct });

  let missingUsageCount = 0;

  for (const autumnPrice of autumnPrices) {
    const stripePrice = findStripePriceFromPrices({
      stripePrices,
      autumnPrice,
    });

    if (isV4Usage({ price: autumnPrice!, cusProduct })) {
      missingUsageCount++;
      continue;
    } else {
      expect(stripePrice).to.exist;
    }
  }

  expect(
    stripePrices.length,
    "number of schedule items equivalent to number of autumn prices",
  ).to.equal(autumnPrices.length - missingUsageCount);
};
